﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Autodesk.Revit.DB;

namespace YArchitech.Revit
{
	public class SearchNode
	{
		public SearchSystem System { get; set; }

		public bool IsLoop { get; set; }

		public Element Element { get; set; }

		public ElementId Id
		{
			get
			{
				if (this.Element != null)
				{
					return this.Element.Id;
				}
				return ElementId.InvalidElementId;
			}
		}

		public Connector In { get; set; }

		public Connector Out { get; set; }

		public Connector Side { get; set; }

		public Connector Side2 { get; set; }

		public int ConnectorCount { get; private set; }

		public IEnumerable<Connector> Connectors { get; private set; }

		public SearchNode PreviousNode { get; set; }

		public SearchNode NextNode { get; set; }

		public SearchNode BranchNode { get; set; }

		public SearchNode Branch2Node { get; set; }

		protected SearchNode(SearchSystem searchSystem)
		{
			this.IsLoop = false;
			this.System = searchSystem;
		}

		public SearchNode(SearchSystem searchSystem, Element element, XYZ pickPoint) : this(searchSystem)
		{
			this.Element = element;
			this.Init(pickPoint);
		}

		public SearchNode(SearchSystem searchSystem, SelectedElement selectedElement) : this(searchSystem)
		{
			this.Element = selectedElement.Element;
			this.Init(selectedElement.SelectedPoint);
		}

		public SearchNode(SearchSystem searchSystem, Connector connectorIn) : this(searchSystem)
		{
			this.Element = connectorIn.Owner;
			this.Init(connectorIn.Origin);
		}

		private void Init(XYZ inPoint)
		{
			if (this.IsLoop)
			{
				Log.Info(new string[]
				{
					"SearchNode Init Is Loop !!! Element Mark is " + this.Element.GetMark()
				});
				return;
			}
			this.Connectors = this.Element.GetConnectors((Connector con) => con.IsEndOrCurveOrPhysical() && con.GetSystemClass() == this.System.SystemInfo.SystemClass);
			this.ConnectorCount = this.Connectors.Count<Connector>();
			Log.Info(new string[]
			{
				"ConnectorCount is " + this.ConnectorCount.ToString()
			});
			if (this.ConnectorCount == 0)
			{
				return;
			}
			this.In = this.Connectors.GetNearestConnector(inPoint);
			if (this.Element.IsMechanicalEquipment())
			{
				this.Init(inPoint, this.Element.GetMEPModel());
				return;
			}
			if (this.ConnectorCount < 2)
			{
				return;
			}
			IOrderedEnumerable<Connector> source = from c in this.Connectors.Except(new Connector[]
			{
				this.In
			})
			orderby c.GetAngleTo(this.In) descending
			select c;
			this.Out = source.First<Connector>();
			if (this.ConnectorCount < 3)
			{
				return;
			}
			IEnumerable<Connector> source2 = source.Except(new Connector[]
			{
				this.Out
			});
			source2.First<Connector>();
			source2 = from side in source2
			orderby side.Flow descending
			select side;
			this.Side = source2.First<Connector>();
			if (this.ConnectorCount < 4)
			{
				return;
			}
			this.Side2 = source2.ElementAt(1);
			if (this.ConnectorCount > 4)
			{
				this.SideOther = source2.Except(new Connector[]
				{
					this.In,
					this.Out,
					this.Side,
					this.Side2
				}).ToList<Connector>();
			}
		}

		private void Init(XYZ inPoint, MEPModel mepModel)
		{
			if (this.ConnectorCount > 2)
			{
				return;
			}
			this.Out = this.Connectors.Except(new Connector[]
			{
				this.In
			}).FirstOrNull<Connector>();
			this.Side = null;
			this.Side2 = null;
			this.SideOther.Clear();
		}

		public void SearchNext()
		{
			SearchSystemFilter searchSystemFilter = (this.System == null) ? SearchSystemFilter.ByName : this.System.Filter;
			Log.Info(new string[]
			{
				"SearchSystemFilter == " + searchSystemFilter.ToString()
			});
			this.SearchNext(new Func<Connector, SearchSystemFilter, Element>(SearchNode.GetNextElementByPosition), searchSystemFilter);
		}

		public void SearchNext(Func<Connector, SearchSystemFilter, Element> searchNextElementWhenNoConnected, SearchSystemFilter searchSystemFilter)
		{
			Log.Info(new string[]
			{
				string.Format("SearchNext  Element Mark  = {0}, IsLoop = {1},  Filter = {2}", this.Element.GetMark(), this.IsLoop, searchSystemFilter)
			});
			this.NextNode = this.GetNextNode(this.Out, searchSystemFilter, searchNextElementWhenNoConnected);
			this.BranchNode = this.GetNextNode(this.Side, searchSystemFilter, searchNextElementWhenNoConnected);
			this.Branch2Node = this.GetNextNode(this.Side2, searchSystemFilter, searchNextElementWhenNoConnected);
			if (this.SideOther != null && this.SideOther.Count > 0)
			{
				foreach (Connector connector in this.SideOther)
				{
					SearchNode nextNode = this.GetNextNode(connector, searchSystemFilter, searchNextElementWhenNoConnected);
					if (nextNode != null)
					{
						this.BranchSideNode.Add(nextNode);
					}
				}
			}
		}

		protected SearchNode GetNextNode(Connector connector, SearchSystemFilter searchSystemFilter, Func<Connector, SearchSystemFilter, Element> searchNextElementWhenNoConnected)
		{
			if (connector == null)
			{
				Log.Warning(new string[]
				{
					"Next Connector is Null "
				});
				return null;
			}
			Element element = SearchNode.GetNextElementByConnect(connector) ?? ((searchNextElementWhenNoConnected == null) ? null : searchNextElementWhenNoConnected(connector, searchSystemFilter));
			if (element == null)
			{
				Log.Warning(new string[]
				{
					"Next Element is Null "
				});
				return null;
			}
			SearchNode searchNode = this.System.GetNode(element);
			if (searchNode == null)
			{
				searchNode = new SearchNode(this.System, element, connector.Origin)
				{
					PreviousNode = this
				};
				this.System.Add(searchNode);
				searchNode.SearchNext(searchNextElementWhenNoConnected, searchSystemFilter);
				return searchNode;
			}
			if (this.PreviousNode == searchNode)
			{
				Log.Warning(new string[]
				{
					"The next node is the previous one, searching goes backwards! "
				});
				return null;
			}
			Log.Info(new string[]
			{
				"GetNextNode is Loop !!! Element Mark is " + searchNode.Element.GetMark()
			});
			searchNode.IsLoop = true;
			return searchNode;
		}

		public static Element GetNextElementByConnect(Connector connector)
		{
			Log.Info(new string[]
			{
				"GetNextElementByConnect  Connector is " + connector.Origin.ToString()
			});
			Connector connectedConnector = connector.GetConnectedConnector();
			if (connectedConnector != null)
			{
				return connectedConnector.Owner;
			}
			return null;
		}

		public static Element GetNextElementByPosition(Connector connector, SearchSystemFilter searchSystemFilter = SearchSystemFilter.ByName)
		{
			if (connector == null)
			{
				return null;
			}
			Document document = connector.Owner.Document;
			XYZ origin = connector.Origin;
			return SearchNode.GetElementByPoint(document, origin, connector, searchSystemFilter) ?? SearchNode.GetNextElementInOtherDocument(connector, searchSystemFilter);
		}

		public static Element GetElementByPoint(Autodesk.Revit.DB.Document doc, XYZ point, string sysName, ElementId exceptId)
		{
			IEnumerable<Element> elements = from e in doc.GetElements(point, SearchNode.SearchRange)
			where e.Id != exceptId && e.GetSystemName() == sysName
			select e;
			IEnumerable<Element> elements2 = from f in doc.GetElements(point, SearchNode.SearchRange)
			where f.Id != exceptId && f.HasConnectors() && f.ContainsSystemName(sysName)
			select f;
			IEnumerable<Connector> source = elements.GetAllConnectors().Concat(elements2.GetAllConnectors());
			Log.Info(new string[]
			{
				"GetElementByPoint_Name  Connectos count == " + source.Count<Connector>()
			});
			Connector nearestConnector = (from con in source
			where con.Origin.DistanceTo(point) < SearchNode.SearchRange / 2.0
			select con).GetNearestConnector(point);
			if (nearestConnector == null)
			{
				return null;
			}
			Log.Info(new string[]
			{
				"GetNextNodeByPosition     Nearest Con is = " + nearestConnector.Origin.ToString()
			});
			return nearestConnector.Owner;
		}

		public static Element GetElementByPoint(Autodesk.Revit.DB.Document doc, XYZ point, ElementId exceptId)
		{
			Log.Info(new string[]
			{
				"GetElementByPoint_None  000 "
			});
			IEnumerable<Element> elements = from e in doc.GetElements(point, SearchNode.SearchRange)
			where e.Id != exceptId
			select e;
			IEnumerable<Element> elements2 = from f in doc.GetElements(point, SearchNode.SearchRange)
			where f.Id != exceptId && f.HasConnectors()
			select f;
			IEnumerable<Connector> source = elements.GetAllConnectors().Concat(elements2.GetAllConnectors());
			Log.Info(new string[]
			{
				"GetElementByPoint_None  Connectos count == " + source.Count<Connector>()
			});
			Connector nearestConnector = (from con in source
			where con.Origin.DistanceTo(point) < SearchNode.SearchRange / 2.0
			select con).GetNearestConnector(point);
			if (nearestConnector == null)
			{
				return null;
			}
			Log.Info(new string[]
			{
				"GetNextNodeByPosition     Nearest Con is = " + nearestConnector.Origin.ToString()
			});
			return nearestConnector.Owner;
		}

		private static IEnumerable<Connector> GetConnectorsBySystemClass(Element element, MEPSystemClassification systemClass)
		{
			if (element == null)
			{
				return Enumerable.Empty<Connector>();
			}
			return from con in element.GetConnectors()
			where con.GetSystemClass() == systemClass
			select con;
		}

		public static Element GetElementByPoint(Autodesk.Revit.DB.Document doc, XYZ point, MEPSystemClassification systemClass, ElementId exceptId)
		{
			IEnumerable<Connector> first = (from e in doc.GetElements(point, SearchNode.SearchRange)
			where e.Id != exceptId
			select e).SelectMany((Element e) => SearchNode.GetConnectorsBySystemClass(e, systemClass));
			IEnumerable<Connector> second = (from f in doc.GetElements(point, SearchNode.SearchRange)
			where f.Id != exceptId
			select f).SelectMany((Element e) => SearchNode.GetConnectorsBySystemClass(e, systemClass));
			IEnumerable<Connector> source = first.Concat(second);
			Log.Info(new string[]
			{
				"GetElementByPoint_SystemClass  Connectos count == " + source.Count<Connector>()
			});
			Connector nearestConnector = (from con in source
			where con.Origin.DistanceTo(point) < SearchNode.SearchRange / 2.0
			select con).GetNearestConnector(point);
			if (nearestConnector == null)
			{
				return null;
			}
			Log.Info(new string[]
			{
				"GetNextNodeByPosition     Nearest Con is = " + nearestConnector.Origin.ToString()
			});
			return nearestConnector.Owner;
		}

		private static Element GetElementByPoint(Autodesk.Revit.DB.Document doc, XYZ point, Connector connector, SearchSystemFilter searchSystemFilter = SearchSystemFilter.ByName)
		{
			Log.Info(new string[]
			{
				"GetElementByPoint Filter is " + searchSystemFilter.ToString()
			});
			Element result = null;
			switch (searchSystemFilter)
			{
			case SearchSystemFilter.None:
				result = SearchNode.GetElementByPoint(doc, point, connector.Owner.Id);
				break;
			case SearchSystemFilter.ByName:
				result = SearchNode.GetElementByPoint(doc, point, connector.GetSystemName(), connector.Owner.Id);
				break;
			case SearchSystemFilter.ByType:
				result = SearchNode.GetElementByPoint(doc, point, connector.GetSystemClass(), connector.Owner.Id);
				break;
			}
			return result;
		}

		public static Element GetNextElementInOtherDocument(Connector connector, SearchSystemFilter searchSystemFilter)
		{
			if (connector == null)
			{
				return null;
			}
			XYZ origin = connector.Origin;
			if (connector.Owner.Document.Equals(RevitUtility.CurrentDocument))
			{
				return SearchNode.GetNextElementInLinkDocument(connector, searchSystemFilter);
			}
			return SearchNode.GetNextElementInCurrentDocument(connector, searchSystemFilter) ?? SearchNode.GetNextElementInLinkDocument(connector, searchSystemFilter);
		}

		public static Element GetNextElementInLinkDocument(Connector connector, SearchSystemFilter searchSystemFilter)
		{
			IEnumerable<LinkedDocument> linkedDocuments = RevitUtility.LinkedDocuments;
			if (linkedDocuments == null || !linkedDocuments.Any<LinkedDocument>())
			{
				return null;
			}
			Log.Info(new string[]
			{
				"GetNextElementInLinkDocument 000 "
			});
			XYZ searchPointInCurDoc = connector.Origin;
			LinkedDocument linkedDocument = linkedDocuments.FirstOrDefault((LinkedDocument lnkDoc) => lnkDoc.Document != null && lnkDoc.Document.Equals(connector.Owner.Document));
			if (linkedDocument != null)
			{
				searchPointInCurDoc = connector.Origin + linkedDocument.Transform.Origin;
			}
			Log.Info(new string[]
			{
				"GetNextElementInLinkDocument  链接文档个数： " + linkedDocuments.Count<LinkedDocument>().ToString()
			});
			IEnumerable<LinkedDocument> enumerable = from lnkDoc in linkedDocuments
			where lnkDoc.Document != null && lnkDoc.ContainsPoint(searchPointInCurDoc)
			select lnkDoc;
			if (!enumerable.Any<LinkedDocument>())
			{
				return null;
			}
			Log.Info(new string[]
			{
				"GetNextElementInLinkDocument  匹配的链接文档个数： " + enumerable.Count<LinkedDocument>().ToString()
			});
			foreach (LinkedDocument linkedDocument2 in enumerable)
			{
				Log.Info(new string[]
				{
					"GetNextElementInLinkDocument  " + linkedDocument2.Document.PathName
				});
				XYZ pointInDocument = linkedDocument2.GetPointInDocument(searchPointInCurDoc);
				Log.XYZ(pointInDocument);
				Element elementByPoint = SearchNode.GetElementByPoint(linkedDocument2.Document, pointInDocument, connector, searchSystemFilter);
				Log.Element(elementByPoint);
				if (elementByPoint != null)
				{
					return elementByPoint;
				}
			}
			return null;
		}

		public static Element GetNextElementInLinkDocumentOld(Connector connectorInCurrentDocument, SearchSystemFilter searchSystemFilter = SearchSystemFilter.ByName)
		{
			Log.Info(new string[]
			{
				"GetNextElementInLinkDocument"
			});
			Document document = connectorInCurrentDocument.Owner.Document;
			if (!document.HasLinkDocuments())
			{
				return null;
			}
			IEnumerable<LinkedDocument> linkedDocuments = document.GetLinkedDocuments();
			Log.Info(new string[]
			{
				"GetNextElementInLinkDocument 2222"
			});
			LinkedDocument linkedDocument = linkedDocuments.FirstOrDefault((LinkedDocument lnkDoc) => lnkDoc.ContainsPoint(connectorInCurrentDocument.Origin));
			Log.Info(new string[]
			{
				"GetNextElementInLinkDocument 3333"
			});
			if (linkedDocument == null)
			{
				return null;
			}
			Log.Info(new string[]
			{
				"GetNextElementInLinkDocument 444"
			});
			XYZ pointInDocument = linkedDocument.GetPointInDocument(connectorInCurrentDocument.Origin);
			XYZ bottomLeft = new XYZ(pointInDocument.X - SearchNode.SearchRange, pointInDocument.Y - SearchNode.SearchRange, pointInDocument.Z - SearchNode.SearchRange);
			XYZ topRight = new XYZ(pointInDocument.X + SearchNode.SearchRange, pointInDocument.Y + SearchNode.SearchRange, pointInDocument.Z + SearchNode.SearchRange);
			document.CreateModelLineBox(bottomLeft, topRight);
			return SearchNode.GetElementByPoint(linkedDocument.Document, pointInDocument, connectorInCurrentDocument, searchSystemFilter);
		}

		public static Element GetNextElementInCurrentDocument(Connector connectorInLinkDocument, SearchSystemFilter searchSystemFilter)
		{
			Element conOwner = connectorInLinkDocument.Owner;
			RevitLinkInstance revitLinkInstance = RevitUtility.CurrentDocument.GetElements<RevitLinkInstance>().FirstOrDefault((RevitLinkInstance ins) => ins.GetLinkDocument().Contains(conOwner.Id));
			if (revitLinkInstance == null)
			{
				return null;
			}
			XYZ point = revitLinkInstance.GetTransform().Origin + connectorInLinkDocument.Origin;
			return SearchNode.GetElementByPoint(RevitUtility.CurrentDocument, point, connectorInLinkDocument, searchSystemFilter);
		}

		public XElement ToXElement()
		{
			XElement xelement = new XElement("Node");
			this.Save(xelement);
			return xelement;
		}

		public void Save(XElement xe)
		{
			if (this.hasSaved)
			{
				return;
			}
			xe.SetAttributeValue("Document", this.Element.Document.PathName);
			xe.SetAttributeValue("Type", this.Element.GetType().ToString());
			xe.SetAttributeValue("Name", this.Element.Name);
			xe.SetAttributeValue("Id", this.Element.Id.IntegerValue);
			xe.SetAttributeValue("Mark", this.Element.GetMark());
			xe.SetAttributeValue("IsLoop", this.IsLoop);
			this.hasSaved = true;
			if (this.NextNode != null)
			{
				xe.Add(this.NextNode.ToXElement());
			}
			if (this.BranchNode != null)
			{
				xe.Add(this.BranchNode.ToXElement());
			}
			if (this.Branch2Node != null)
			{
				xe.Add(this.Branch2Node.ToXElement());
			}
			if (this.BranchSideNode.Count > 0)
			{
				foreach (SearchNode searchNode in this.BranchSideNode)
				{
					xe.Add(searchNode.ToXElement());
				}
			}
		}

		public List<Connector> SideOther = new List<Connector>();

		public List<SearchNode> BranchSideNode = new List<SearchNode>();

		private static readonly double SearchRange = RevitElementExtension.PointEqualTol;

		private bool hasSaved;
	}
}
